package springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import springcloud.entity.Member;
import springcloud.entity.Result;
import springcloud.handler.CustomGlobalBlockHandler;
import springcloud.handler.CustomGlobalFallbackHandler;
import springcloud.service.MemberService;

import javax.annotation.Resource;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("all")
@RestController
@Slf4j
public class MemberController {

    @Resource
    private MemberService memberService;
    //执行的计数器-static静态
    private static int num = 0;
    /**
     * 流量控制模式---关联
     * 当调用 /t2 API 接口时，如果 QPS 超过 1，这时调用 /t1 API 接口 直接失败，抛异常.
     * 其中， /t2 是关联的资源 , 限流的资源是/t1【在t1上设置限流规则】
     * @return
     */
    @GetMapping("/t1")
    public Result t1() {
        return Result.success("t1()执行..");
    }
    @GetMapping("/t2")
    public Result t2() {
        //让线程休眠1s, 模拟执行时间为1s --为了测试 排队效果
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //输出线程信息
        log.info("执行t2() 线程id={}", Thread.currentThread().getId());
        return Result.success("t2()执行..");
    }

    //t3接口用来测试 熔断降级实例---慢调用比例
    @GetMapping("/t3")
    public Result t3() {
        //让线程休眠300ms毫秒, 模拟执行时间
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Result.success("t3()执行...");
    }

    //t4接口用来测试 熔断降级实例---异常比例
    @GetMapping("/t4")
    public Result t4() {
        //设计异常比例达到50% > 20%
        if (++num % 2 == 0) {
            //制造一个异常
            System.out.println(3 / 0);
        }
        log.info("熔断降级测试[异常比例] 执行t4() 线程id={}",
                Thread.currentThread().getId());
        return Result.success("t4()执行...");
    }

    //设计一个测试案例-满足异常数的阈值，触发限流机制【熔断降级实例---异常数】
    @GetMapping("/t5")
    public Result t5() {
        //老韩解读
        //出现10次异常, 这里需要设置大于6, 需要留出几次做测试和 加入簇点链路【第一次请求】
        if (++num <= 10) {
            //制造一个异常
            System.out.println(3 / 0);
        }
        log.info("熔断降级测试[异常数] 执行t5() 线程id={}",
                Thread.currentThread().getId());
        return Result.success("t5()执行了...");
    }

    /**
     * value = "t6" 表示 sentinel限流资源的名字
     * blockHandlerClass = CustomGlobalBlockHandler.class : 全局限流处理类
     * blockHandler = "handlerMethod1": 指定使用全局限流处理类哪个方法，来处理 限流信息
     * fallbackClass = CustomGlobalFallbackHandler.class: 全局fallback处理类
     * fallback = "fallbackHandlerMethod1": 指定使用全局fallback处理类哪个方法来处理 java异常/业务异常
     * exceptionsToIgnore = {NullPointerException.class}: 表示如果t6()抛出 NullPointerException,
     * 就使用系统默认方式处理。不交给 CustomGlobalFallbackHandler 全局fallback处理类 来处理。
     * @return
     */
    //使用全局限流处理类，显示限流信息
    @GetMapping("/t6")
    @SentinelResource(value = "t6",
            fallbackClass = CustomGlobalFallbackHandler.class,
            fallback = "fallbackHandlerMethod1",
            blockHandlerClass = CustomGlobalBlockHandler.class,
            blockHandler = "handlerMethod1",
            exceptionsToIgnore = {NullPointerException.class})
    public Result t6() {
        //假定: 当访问t6资源次数是5的倍数时，就出现java异常
        if (++num % 5 == 0) {
            throw new NullPointerException("null指针异常 num=" + num);
        }
        if (num % 6 == 0) {//当访问t6资源次数是6的倍数时,抛出 runtime异常
            throw new RuntimeException("RuntimeException num=" + num);
        }

        log.info("执行t6() 线程id={}", Thread.currentThread().getId());
        return Result.success("200", "t6()执行OK~~");
    }

    //完成对 热点key限流 的测试 /news?id=x&type=x
    /**
     * 老师解读
     * 1.@SentinelResource : 指定sentinel限流资源
     * 2.value = "news" 表示sentinel限流资源 名称,由程序员指定
     * 3. blockHandler = "newsBlockHandler": 当出现限流时，由newsBlockHandler方法进行处理
     */
    @GetMapping("/news")
    @SentinelResource(value = "news", blockHandler = "newsBlockHandler")
    public Result queryNews(@RequestParam(value = "id", required = false) String id,
                            @RequestParam(value = "type", required = false) String type) {
        //在实际开发中, 新闻应该到DB或者缓存获取,老师这里就模拟
        log.info("到DB 查询新闻");
        return Result.success("返回id=" + id + " 新闻 from DB");
    }

    //热点key限制/限流异常处理方法
    public Result newsBlockHandler(String id, String type, BlockException blockException) {
        return Result.success("查询id=" + id + " 新闻 触发热点key限流保护 sorry...");
    }

    /**
     * 编写方法，完成数据添加
     * 1. 前端如果是以json格式来发送添加信息furn， 那么需要使用 @RequestBody
     * , 才能将数据封装到对应的bean。同时保证http的请求头的 content-type 是对应的 application/json
     * 2. 如果前端是以表单形式【或地址栏参数形式】提交数据，会自动进行对象参数封装，不需要使用 @RequestBody。
     * 同时保证http请求头的 content-type 是对应的 multipart/form-data
     * @param member
     * @return
     */
    @PostMapping("/member/save")
    public Result save(@RequestBody Member member) {
        log.info("member_provider_consumer member={}", member);
        int affected = memberService.save(member);
        if (affected > 0) {   // 保存成功
            return Result.success("member-service-nacos-provider-10004 添加会员成功", affected);
        } else {
            return Result.error("401", "添加会员失败");
        }

    }

    /**
     * 根据id查询会员信息。
     * 使用url占位符+@PathVariable
     * @param id
     * @return
     */
    //学习springmvc讲过-通过?携带参数方式。通过直接对 /member/query 进行限流
    //@GetMapping(value = "/member/query", params = "id")
    //public Result queryMemberById(Long id) {
    @GetMapping("/member/query/{id}")
    public Result queryMemberById(@PathVariable("id") Long id) {

        //模拟超时, 让线程休眠1s【验证通过控制线程数进行流量控制】
//        try {
//            TimeUnit.MILLISECONDS.sleep(1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        //输出线程的情况
//        log.info("enter 10004 getMemberById 当前线程id={} 时间={}",
//                Thread.currentThread().getId(), new Date());

        Member member = memberService.queryMemberById(id);
        if (member != null) {
            return Result.success("member-service-nacos-provider-10004 查询会员成功", member);
        } else {
            return Result.error("402", "ID= " + id + "不存在");
        }
    }
}
